package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.entity.dto.CouponDiscountDTO;
import com.tianji.promotion.entity.dto.OrderCourseDTO;
import com.tianji.promotion.entity.dto.UserCouponDTO;
import com.tianji.promotion.entity.po.Coupon;
import com.tianji.promotion.entity.po.ExchangeCode;
import com.tianji.promotion.entity.po.UserCoupon;
import com.tianji.promotion.entity.query.UserCouponQuery;
import com.tianji.promotion.entity.vo.CouponVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author lemon
 */
@Slf4j
//@Service
//@RequiredArgsConstructor
public class UserCouponWithRedissonServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private  CouponMapper couponMapper;

    private IExchangeCodeService exchangeCodeService;

    private RedissonClient redissonClient;

    /**
     * 领取优惠券
     *
     * @param couponId
     */
    @Override
    public void receiveCoupon(Long couponId) {
        Coupon coupon = couponMapper.selectById(couponId);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        // 2.校验发放时间
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券发放已经结束或尚未开始");
        }
        // 3.校验库存
        if (coupon.getIssueNum() >= coupon.getTotalNum()) {
            throw new BadRequestException("优惠券库存不足");
        }
        Long userId = UserContext.getUser();

        // 加锁，让锁在事务之前，这样事务就能完整提交
        String key = "lock:coupon:uid:" + userId;
        // 底层是可重入锁
        RLock lock = redissonClient.getLock(key);
        try {
            // 如果你加时间，看门狗机制就会失效
            boolean tryLock = lock.tryLock();
            if (!tryLock) {
                throw new BizIllegalException("操作太频繁，请稍后再试");
            }
            IUserCouponService currentProxy = (IUserCouponService) AopContext.currentProxy();
            // 这个不传第三个表示不是兑换码兑换的，也就不用去修改兑换码的状态了
            currentProxy.checkAndCreateUserCoupon(coupon, userId, null);

        } finally {
            // redisson的话里面是有判断的
            // 并且是原子操作，不会出现异常的情况下，锁不会自动释放
            lock.unlock();
        }

    }

    /**
     * 我的优惠券列表-分页查询-用户端
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponVO> queryMyCouponPage(UserCouponQuery query) {
        Long userId = UserContext.getUser();
        Page<UserCoupon> page = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPage(new OrderItem("term_end_time", true)));
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 获取优惠券信息先得id
        Set<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        // 3.2.查询
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);

        // 4.转换
        List<CouponVO> couponVOS = coupons.stream().map(uc -> {
            return BeanUtils.copyBean(uc, CouponVO.class);
        }).collect(Collectors.toList());


        return PageDTO.of(page, couponVOS);
    }

    /**
     * 兑换码兑换优惠券
     *
     * @param code
     */
    @Override
    public void exchangeCoupon(String code) {
        // 1.检验code
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("兑换码不能为空");
        }

        // 2.解析校验码
        // 自增id
        long serialNum = CodeUtil.parseCode(code);
        // 采用redis中 setbit key offset 1的方式来判断兑换码是否已经兑换
        // serialNum表示偏移量
        boolean result = exchangeCodeService.updateExchangeCodeMark(serialNum, true);

        // 3.判断兑换码是否已经兑换  采用
        if (result) {
            // 说明已经被兑换了
            throw new BizIllegalException("兑换码已经被兑换");
        }
        try {
            // 4.根据自增id查询兑换码信息
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (exchangeCode == null) {
                throw new BizIllegalException("兑换码不存在");
            }

            // 5.判断是否过期
            LocalDateTime now = LocalDateTime.now();
            if (now.isAfter(exchangeCode.getExpiredTime())) {
                throw new BizIllegalException("兑换码已经过期");
            }

            // 6.校验并生成用户券
            Long userId = UserContext.getUser();
            // 6.1.优惠券信息
            // 6.2.这个就是兑换码对应的优惠券
            Long couponId = exchangeCode.getExchangeTargetId();
            Coupon coupon = couponMapper.selectById(couponId);
            if (coupon == null) {
                throw new BizIllegalException("优惠券不存在");
            }

            // 控制防止重复提交
            // 以及事务的控制
            String key = "lock:coupon:exchange:uid:" + userId;
            RLock lock = redissonClient.getLock(key);
            boolean tryLock = lock.tryLock();
            if (!tryLock) {
                throw new BizIllegalException("操作太频繁，请稍后再试");
            }
            IUserCouponService currentProxy = (IUserCouponService) AopContext.currentProxy();
            // 6.3.校验并生成用户券，更新兑换码状态
            currentProxy.checkAndCreateUserCoupon(coupon, userId, (int) serialNum);

        } catch (Exception e) {
            // 将兑换码状态进行重置
            // 采用redis中 setbit key offset 0的方式来判断兑换码是否已经兑换
            exchangeCodeService.updateExchangeCodeMark(serialNum, false);
            // 还要抛出异常
            throw e;
        }

    }


    /**
     * 校验并生成用户券
     *
     * @param coupon
     * @param userId
     * @param serialNum
     */
    @Transactional
    public void checkAndCreateUserCoupon(Coupon coupon, Long userId, Integer serialNum) {
//        synchronized (userId.toString().intern()) {
        // 4.校验每人限领数量
        // 4.1.统计当前用户对当前优惠券的已经领取的数量
        Integer count = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        // 4.2.校验限领数量
        if (count != null && count >= coupon.getUserLimit()) {
            throw new BadRequestException("超出领取数量");
        }
        // 5.更新优惠券的已经发放的数量 + 1
        couponMapper.incrIssueNum(coupon.getId());
        // 6.新增一个用户券
        saveUserCoupon(coupon, userId);

        // 7.更新兑换码状态
        if (serialNum != null) {
            // 从db里面修改
            exchangeCodeService.lambdaUpdate().set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .set(ExchangeCode::getUserId, userId)
                    .eq(ExchangeCode::getId, serialNum)
                    .update();
        }
//        }

    }

    @Override
    public void checkAndCreateUserCouponNew(UserCouponDTO dto) {

    }

    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        return null;
    }

    @Override
    public List<String> queryDiscountRules(List<Long> userCouponIds) {
        return null;
    }

    /**
     * 保存用户券
     *
     * @param coupon
     * @param userId
     */
    private void saveUserCoupon(Coupon coupon, Long userId) {

        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());
        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime == null && termEndTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);
        // 3.保存
        save(uc);
    }
}
